/*
 * Copyright (C) 2013 Daniel Wasilew <daniel.wasilew@codeminer.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.codeminer.parts.database.derby;

import org.apache.derby.tools.ij;
import org.codeminer.database.DatabaseConnection;
import org.codeminer.database.DatabaseException;
import org.codeminer.database.DatabaseManager;
import org.codeminer.database.DatabaseUpdateStep;
import org.codeminer.parts.application.ApplicationComponent;
import org.codeminer.parts.application.ApplicationContext;
import org.codeminer.parts.application.ShutdownCallback;
import org.codeminer.parts.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * @author Daniel Wasilew <daniel.wasilew@codeminer.org>
 */
public final class DerbyDatabaseManager extends DatabaseManager implements ApplicationComponent {

    private static final int CURRENT_DB_VERSION = 8;
    private static final Logger log = LoggerFactory.getLogger(DerbyDatabaseManager.class);
    private DatabaseConnection connection;
    private ApplicationContext context;

    @Override
    public void startup(ApplicationContext context, ShutdownCallback shutdownCallback) throws Exception {
        this.context = context;

        initializeUpdateSteps();
        start();
    }

    @Override
    public void shutdown() {
        try {
            shutdownDatabase();
        } catch (DatabaseException ex) {
            log.error("could not shutdown database", ex);
        }
    }

    @Override
    public void shutdownDatabase() throws DatabaseException {
        try {
            DriverManager.getConnection("jdbc:derby:;shutdown=true");
        } catch (SQLException ex) {
            if (!ex.getSQLState().equals("XJ015")) {
                throw new DatabaseException(ex);
            }
        }
    }

    @Override
    protected boolean databaseExists() throws DatabaseException {
        ResultSet executeQuery = null;
        try {
            executeQuery = getDatabaseConnection().createStatement().executeQuery("SELECT * FROM DATABASEINFORMATION");
            executeQuery.next();
            executeQuery.getString(1);
            return true;
        } catch (DatabaseException | SQLException ex) {
            //simple test if database exists
        } finally {
            try {
                if (executeQuery != null) {
                    executeQuery.close();
                }
            } catch (SQLException ex) {
                log.trace("could not close statement", ex);
            }
        }
        return false;
    }

    @Override
    protected void initializeDatabase() throws DatabaseException {
        try {
            InputStream resource = new FileInputStream(new File(context.getDatabaseScriptDir(), context.getApplicationPart(Settings.class).getCurrentSQLScriptLocation()));
            ij.runScript(getDatabaseConnection().getConnection(), resource, "UTF-8", System.out, "UTF-8");
        } catch (UnsupportedEncodingException | FileNotFoundException ex) {
            throw new DatabaseException(ex);
        }
    }

    @Override
    public void loadDatabase() throws DatabaseException {
        // nothing to do here
    }

    @Override
    public int getSupportedDatabaseVersion() {
        return CURRENT_DB_VERSION;
    }

    @Override
    public int getCurrentDatabaseVersion() {
        ResultSet executeQuery = null;
        try {
            executeQuery = getDatabaseConnection().createStatement().executeQuery("SELECT * FROM DATABASEINFORMATION");
            while (executeQuery.next()) {
                final String id = executeQuery.getString(1);
                if (id.equals("Version")) {
                    return executeQuery.getInt(2);
                }
            }
            return -1;
        } catch (DatabaseException | SQLException ex) {
            log.error("could not get Version from Database", ex);
        } finally {
            try {
                if (executeQuery != null) {
                    executeQuery.close();
                }
            } catch (SQLException ex) {
                log.trace("could not close statement", ex);
            }
        }
        return -1;
    }

    @Override
    protected DatabaseConnection getDatabaseConnection() throws DatabaseException {
        if (context.getDatabaseDir() == null) {
            throw new IllegalArgumentException("databaseDir == null");
        }

        if (this.connection == null || this.connection.isClosed()) {
            try {

                Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
                connection = new DatabaseConnection(DriverManager.getConnection("jdbc:derby:" + context.getDatabaseDir().getAbsolutePath() + ";create=true"));
            } catch (SQLException | InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
                throw new DatabaseException(ex);
            }
        }
        return connection;

    }

    @Override
    public void clearDatabase() throws DatabaseException {
        Statement statement = null;
        try {
            statement = getDatabaseConnection().createStatement();
            statement.executeUpdate("DELETE FROM SONGS_FOR_PLAYLIST");
            statement.executeUpdate("DELETE FROM SONGS_FOR_ALBUM");
            statement.executeUpdate("DELETE FROM ARTIST_FOR_SONGS");
            statement.executeUpdate("DELETE FROM ARTISTS");
            statement.executeUpdate("DELETE FROM SONGS");
            statement.executeUpdate("DELETE FROM ALBUMS");
            statement.executeUpdate("DELETE FROM ARTWORKS");
            statement.executeUpdate("DELETE FROM PLAYLISTS");
            statement.executeUpdate("DELETE FROM USERS");

        } catch (SQLException ex) {
            throw new DatabaseException(ex);
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException ignored) {
                }
            }
        }
    }

    @Override
    public Class getDescriptionClass() {
        return DatabaseManager.class;
    }

    private void initializeUpdateSteps() {
        File upgradeScriptFolder = new File(context.getDatabaseScriptDir(), context.getApplicationPart(Settings.class).getSQLUpdateScriptLocations());

        log.trace("initializing migration of database with scripts found in [" + upgradeScriptFolder + "]");
        File[] upgradeFiles = upgradeScriptFolder.listFiles();
        if (upgradeFiles != null) {
            for (final File file : upgradeFiles) {
                final String fileName = file.getName();
                final Integer from = Integer.parseInt(fileName.substring(0, fileName.indexOf('-')));
                final Integer to = Integer.parseInt(fileName.substring(fileName.indexOf("-") + 1, fileName.indexOf(".")));
                addUpdateStep(new DatabaseUpdateStep() {

                    @Override
                    public Comparable from() {
                        return from;
                    }

                    @Override
                    public Comparable to() {
                        return to;
                    }

                    @Override
                    public void execute(DatabaseConnection databaseConnection) throws DatabaseException {
                        try {
                            ij.runScript(databaseConnection.getConnection(), new FileInputStream(file), "UTF-8", System.out, "UTF-8");
                        } catch (UnsupportedEncodingException | FileNotFoundException ex) {
                            throw new DatabaseException(ex);
                        }
                    }
                });
            }
        } else {
            log.info("no upgrade files found in [{}]", upgradeScriptFolder.getAbsolutePath());
        }
    }
}
